ERC1155: Crypto Item Standard
https://gyazo.com/eb0a00b4f02c5501cf49bd9f5126bef3
Disclaimer: まだ議論されているアレです
Disclaimer: 継承関係をちょっと勘違いしていてひどい理解をしていたかもなので考察中
contract Hoge is ERC20, ERC721, ERC1155 { とすればcompatibleになる?
まとめ
ERC20 + ERC721をどちらも扱えるようにしたToken StandardというよりはGame Contract Standardに近い印象
マスタ管理するデータ(コンテンツ)が沢山存在し、コンテンツの交換が多対多で行われるものには向いていそう
ERC1155に対応しておけば他の対応しているゲームとのつなぎこみは楽そう?に見えるので、その点ではゲームデベロッパーにもメリットがありそう
が、ゲームごとに管理データって違うしなぁ...CryptoGameの文脈でどう広まっていくのかをウォッチしたい
概要
ゲームのユースケースに合わせた標準規格
Enjinというゲームxブロックチェーンのプラットフォームが提案している
1つのコントラクトの中にitem同士を区別できるIDと、metadataのURLを一緒に保存する
コントラクトはtokenIDごとの設定値とItemのCollectionの振る舞いを持つ
仕様提案に至るモチベーション
ERC721が出てきたけれど、メインストリームのゲームのユースケースに適したものがなかった
When we started building Enjin Coin one year ago, we realized that the current token standards would not work for the items that mainstream games use.
1タイトルで100000種類のアイテムぐらいあるゲームをリリースしようとした場合、全部をトークン化してデプロイしてゲームスタジオが管理するのは現実的でない
少なくとも10000アイテムくらいあるゲームはまあありそう
マルチプレイのゲームの多くはアイテム数が多い
Runescape has 35,000 and World of Warcraft, the king of MMOs has over 100,000 different items! Shooters like Overwatch and Team Fortress 2 each have thousands of skins and items.
問題意識
Before ERC1155
取引所(ゲームの場合はゲーム運営?)を介さずにアイテムの交換を行いたい?
そもそもAtomic Swapするとなるとトランザクション数多すぎるよね問題
(Bitcoin の場合は) Bail In / Bail Out Transactionつくったりなど
下図だとToken A / Token Bのようにゲーム内部資産を
https://gyazo.com/80c085a20635f1192756c7c8607ed750
Ref: Atomic Swapおさらいはこちら
いつもお世話になっております(作者どんな方なんやろうなぁ...
What ERC1155 realizes
1コントラクトで色々なItemを管理できるようにする
自然な感じがする(少なくとも都度トークンを発行するよりは)
https://gyazo.com/5614dde0a140a48528bee41a3006c8ed
ERC1155を利用することで複数アイテムを複数の受け取り手に対して1トランザクションで受け渡せるようにする
https://gyazo.com/ce8149a6ec0369db352253dc7067c6cb
Non Fungible Token と Fungible Tokenを一緒に扱う
Fungible Item : like stims(stims = stimulant) health kits(回復剤), or stacks of ammunition(弾薬)
武器とか (gun, sword) も基本的にはFungible
武器の使用履歴とか出処(provenance)を記録したい時、そういう武器はNon Fungible Tokenとして扱える
ERC20 / ERC721を使う場合
ERC20 ... Fungibleなものにしか使えない (idがないので)
ERC721 ... idを持つような、それぞれ固有なものにだけ用いれる
ERC20 / ERC721はお互いに互換性がないし、混ぜて使うこともできない
ERC1155では?
同一コントラクトで大量のitemを扱える
必要に応じてunique indexをつけてNon Fungibleにも, Fungibleにも扱える
EnjinはERC1155をブロックチェーンに詳しくない開発者でも手軽に作ることができる機能を提供する。
モチベーションがわかったところで仕様と実装を追っていく
ERC721等と同様にReceivableやEnumerableも提示されている
提示されているもの
IERC1155.sol
IERC1155TokenReceiver
IERC1155Extended
IERC1155BatchTransfer
IERC1155BatchTransferExtended
IERC1155Operators
IERC1155Views // name()とか定義されているこのViewを継承してもERC20互換にはならない
memo: syntax higlightつかいたいのでjsにしてます
safeBatchTransferFrom .. これがまとめて送る君
やりたいことは分かるもの、具体的な管理法を見ないとピンとこないので実装を追っていく
code: ERC1155.js
contract ERC1155 is IERC1155, IERC1155Extended, IERC1155BatchTransfer, IERC1155BatchTransferExtended {
using SafeMath for uint256;
using Address for address;
// Variables
struct Items {
string name;
uint256 totalSupply;
mapping (address => uint256) balances;
}
mapping (uint256 => uint8) public decimals;
mapping (uint256 => string) public symbols;
mapping (uint256 => mapping(address => mapping(address => uint256))) public allowances;
mapping (uint256 => Items) public items; // ItemIDを振ってよしなに管理するっぽい(ゲームマスタと一緒?)
mapping (uint256 => string) public metadataURIs;
ERC20っぽいやつはItemIDを振って管理するようだ(ゲームコントラクトを作る場合はどのみちそうするだろう)
このERC1155のERC20っぽく管理されているやつはやつはERC20の仕様を満たすのだろうか?
https://gyazo.com/6597752fabffb1753fca480e7e984fef
ERC1155では下記のように
code: ERC1155vsERC20.js
interface IERC1155Extended {
function transfer(address _to, uint256 _id, uint256 _value) external;
function safeTransfer(address _to, uint256 _id, uint256 _value, bytes _data) external;
}
interface IERC1155 {
function balanceOf(uint256 _id, address _owner) external view returns (uint256);
function allowance(uint256 _id, address _owner, address _spender) external view returns (uint256);
ItemのIDを指定しないといけないのでI/Fが異なる => ERC1155を満たしていてもERC20としては扱えない(そりゃそうだ感はある。ERC1155を満たしているゲーム内アイテムの一部だけを普通のウォレットで扱う、などはできないだろう。それ故Enjin Wallet的なものが必要になる。)
ERC721を満たしているかと言うとそうでもない(Eventに関しては一致しているが、safeTransferFromが異なる)
code: ERC721-Event.js
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
/*
interface IERC1155 {
event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _oldValue, uint256 _value);
*/
// ERC721のsafeTransferFrom
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
// 参考: ERC1155のsafeTransferFrom : インターフェイスが違うorz
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes _data) external;
NameとかもID指定でViewする (のでERC20互換ではない)
code: ERC1155View.js
interface IERC1155Views {
function totalSupply(uint256 _id) external view returns (uint256);
function name(uint256 _id) external view returns (string);
function symbol(uint256 _id) external view returns (string);
function decimals(uint256 _id) external view returns (uint8);
function uri(uint256 _id) external view returns (string);
}
ここまで見ていただいたように、他のAssetをERC1155側に持ち込んだり、ERC1155のAssetを外で使えるようにするということではなく、まとめて取り扱いたいから、「色々なアイテムを取り扱うContractのStandard作りました〜」的なノリを感じます。
参考 下記は提案されているInterface
code: IERC1155.js
pragma solidity ^0.4.24;
/// @dev Note: the ERC-165 identifier for this interface is 0xf23a6e61.
interface IERC1155TokenReceiver {
/// @notice Handle the receipt of an ERC1155 type
/// @dev The smart contract calls this function on the recipient
/// after a safeTransfer. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called safeTransferFrom function
/// @param _from The address which previously owned the token
/// @param _id The identifier of the item being transferred
/// @param _value The amount of the item being transferred
/// @param _data Additional data with no specified format
/// @return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))
/// unless throwing
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes _data) external returns(bytes4);
}
interface IERC1155 {
event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _oldValue, uint256 _value);
event Transfer(address _spender, address indexed _from, address indexed _to, uint256 indexed _id, uint256 _value);
function transferFrom(address _from, address _to, uint256 _id, uint256 _value) external;
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes _data) external;
function approve(address _spender, uint256 _id, uint256 _currentValue, uint256 _value) external;
function balanceOf(uint256 _id, address _owner) external view returns (uint256);
function allowance(uint256 _id, address _owner, address _spender) external view returns (uint256);
}
interface IERC1155Extended {
function transfer(address _to, uint256 _id, uint256 _value) external;
function safeTransfer(address _to, uint256 _id, uint256 _value, bytes _data) external;
}
interface IERC1155BatchTransfer {
function batchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values) external;
function safeBatchTransferFrom(address _from, address _to, uint256[] _ids, uint256[] _values, bytes _data) external;
function batchApprove(address _spender, uint256[] _ids, uint256[] _currentValues, uint256[] _values) external;
}
interface IERC1155BatchTransferExtended {
function batchTransfer(address _to, uint256[] _ids, uint256[] _values) external;
function safeBatchTransfer(address _to, uint256[] _ids, uint256[] _values, bytes _data) external;
}
interface IERC1155Operators {
event OperatorApproval(address indexed _owner, address indexed _operator, uint256 indexed _id, bool _approved);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function setApproval(address _operator, uint256[] _ids, bool _approved) external;
function isApproved(address _owner, address _operator, uint256 _id) external view returns (bool);
function setApprovalForAll(address _operator, bool _approved) external;
function isApprovedForAll(address _owner, address _operator) external view returns (bool isOperator);
}
interface IERC1155Views {
function totalSupply(uint256 _id) external view returns (uint256);
function name(uint256 _id) external view returns (string);
function symbol(uint256 _id) external view returns (string);
function decimals(uint256 _id) external view returns (uint8);
function uri(uint256 _id) external view returns (string);
}
code: IERC1155NonFungible.js
pragma solidity ^0.4.24;
interface IERC1155NonFungible {
// Optional Functions for Non-Fungible Items
function ownerOf(uint256 _id) external view returns (address);
function nonFungibleByIndex(uint256 _id, uint128 _index) external view returns (uint256);
function nonFungibleOfOwnerByIndex(uint256 _id, address _owner, uint128 _index) external view returns (uint256);
function isNonFungible(uint256 _id) external view returns (bool);
}
NFTとFT同時に扱う場合の処理
NFTとFTを同時に扱うコントラクトの場合は
ERC1155NonFungibleとNonFungibleMintableを継承して用いる
code: NonFungible.js
// 内部でNonFungible確認して処理を切り替える
function balanceOf(uint256 _id, address _owner) external view returns (uint256) {
if (isNonFungibleItem(_id))
return ownerOf(_id) == _owner ? 1 : 0;
uint256 _type = _id & TYPE_MASK;
}
function totalSupply(uint256 _id) external view returns (uint256) {
// return 1 for a specific nfi, totalSupply otherwise.
if (isNonFungibleItem(_id)) {
// Make sure this is a valid index for the type.
return 1;
} else {
return items_id.totalSupply; }
}
NFTをmintするときはIDを工夫するサンプル実装になっている(IDの発番のしかたでNFT/FTを判定する)
code: MintableNonFungible.js
// Use a split bit implementation.
// Store the type in the upper 128 bits..
uint256 constant TYPE_MASK = uint256(uint128(~0)) << 128;
// ..and the non-fungible index in the lower 128
uint256 constant NF_INDEX_MASK = uint128(~0);
code: MintNonFungibe.js
function mintNonFungible(uint256 _type, address[] _to) external minterOnly(_type) {
require(isNonFungible(_type));
// Index are 1-based.
uint256 _startIndex = items_type.totalSupply + 1; for (uint256 i = 0; i < _to.length; ++i) {
uint256 _nfi = _type | (_startIndex + i);
}
items_type.totalSupply = items_type.totalSupply.add(_to.length); }
Atomic Swapどこいった?
Engin Coinのbatch transferの実装を見てみる
ERC20っぽいやつとERC721っぽいやつを実装上透過的に扱っているので1トランザクションで処理できるといえばできる。
contractA is ERC1155
contractB is ERC1155
code: batchTransfer.js
function batchTransfer(address _to, uint256[] _ids, uint256[] _values) external {
uint256 _id;
uint256 _value;
for (uint256 i = 0; i < _ids.length; ++i) {
emit Transfer(msg.sender, msg.sender, _to, _id, _value);
}
}
function safeBatchTransfer(address _to, uint256[] _ids, uint256[] _values, bytes _data) external {
this.batchTransfer(_to, _ids, _values);
for (uint256 i = 0; i < _ids.length; ++i) {
// solium-disable-next-line arg-overflow
require(_checkAndCallSafeTransfer(msg.sender, _to, _idsi, _valuesi, _data)); }
}
code: 多対多.js
function multicastTransfer(address[] _to, uint256[] _ids, uint256[] _values) external {
for (uint256 i = 0; i < _to.length; ++i) {
uint256 _value = _valuesi; emit Transfer(msg.sender, msg.sender, _dst, _id, _value);
}
}
Enjinって?
Enjinが提案しているやつ
Enjin
Querterly updateがMediumに書いてあるので後でまとめる
Enjin Coin
References
mosa_siru.icon < token contractじゃなくてトークン管理マンcontract 感